<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Creating factory pattern
 */

namespace crcl\base\instance;

interface IFactory
{
  /**
   * Returns new instance
   * uses sFactoryDirectory (default: container), if defined in child class
   *
   * @static
   *
   * @param string  $sUri
   *
   * @return $oInstance
   */
  public static function getInstance($sUri);

  /**
   * factory is called from child class.
   *
   * @static
   *
   * @param string  $sUri
   *
   * @return $oInstance
   */
  public static function factory($sUri);
}

abstract class Factory implements IFactory
{
  const PREFIX = 'Container';
  const FACTORY_DIRECTORY_DEFAULT = 'container';

  public static function getInstance($sUri)
  {
    if (\method_exists(\get_called_class(), 'factory'))
    {
      $sClass = \get_called_class();
      $oInstance = $sClass::factory($sUri);
      return $oInstance;
    }
    //we should get a fatal error, if this happens ;)
    // @codeCoverageIgnoreStart
    throw new Exception('Factory is missing',
      Exception::ERROR_FACTORY_MISSING, array('uri' => $sUri));
    // @codeCoverageIgnoreEnd
  }

  public static function factory($sUri)
  {
    $sClass = 'self';
    if (\method_exists(\get_called_class(), 'checkUri'))
    {
      $sClass = \get_called_class();
    }

    $sType = $sClass::checkUri($sUri);

    $sDirectory = self::getFactoryDirectory();
    $sNamespace = self::getNamespaceFromCalledClass();

    $sDirectory = \str_replace('/', '\\', $sDirectory);
    $sClass = $sNamespace . '\\' . $sDirectory . '\\' . $sType;

    $oInstance = new $sClass($sUri);
    return $oInstance;
  }

  /** PROTECTED ***********************************************************************************/

  /**
   * Returns type for factory: scheme of URI
   * Override for more advanced usages
   */
  protected static function getTypeFromUri($sUri)
  {
    $sType = \crcl\base\resource\Utils::getScheme($sUri);
    $sType = \ucfirst($sType);
    return $sType;
  }

  /**
   * Checks if uri is valid. declare valid schemes in child class.
   * override for more advanced checks
   * uses static var aAllowedTypes
   * returns type (scheme) if uri is valid, otherwise throws exception
   *
   * @param  string $sUri URI to check
   *
   * @throws Exception Exception::ERROR_FACTORY_INVALIDTYPE
   *
   * @return string $sType
   */
  protected static function checkUri($sUri)
  {
    $sType = self::getTypeFromUri($sUri);
    $child = \get_called_class();

    if (isset($child::$aFactoryAllowedTypes))
    {
      $aAllowedType = array();
      foreach ($child::$aFactoryAllowedTypes as $sAllowedType)
      {
        $aAllowedType[] = \strtolower($sAllowedType);
      }
      if (!\in_array(\strtolower($sType), $aAllowedType))
      {
        throw new Exception(
          'Invalid type',
          Exception::ERROR_INVALIDTYPE,
          array('uri' => $sUri, 'type' => $sType, 'allowed' => $child::$aFactoryAllowedTypes));
      }
    }
    $sType = self::PREFIX . $sType;
    return $sType;
  }

  protected static function getNamespaceFromCalledClass()
  {
    $sClass = \get_called_class();

    $sClass = \substr($sClass, 0, \strrpos($sClass, '\\'));
    return $sClass;
  }

  protected static function getFactoryDirectory()
  {
    $child = \get_called_class();
    if (isset($child::$sFactoryDirectory))
    {
      return $child::$sFactoryDirectory;
    }
    $sDirectory = self::FACTORY_DIRECTORY_DEFAULT;
    return $sDirectory;
  }
}